﻿using BezierApproximations.ViewModels;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Telerik.Windows.Documents.Utilities;

namespace BezierApproximations
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private const double Padding = 20;
        private double boundingDiameter;
        private double boundingRadius;
        private Size ellipseSize;
        private Size ellipseRadiuses;
        private Point canvasOffset;
        private Point segmentStart;
        private Point segmentEnd;
        private bool isLargeArc;
        private SweepDirection sweepDirection;
        private double xAxisRotation;
        private Geometry pathData;
        private readonly ObservableCollection<PointViewModel> points;
        private readonly ObservableCollection<LineViewModel> lines;

        public MainWindow()
        {
            this.points = new ObservableCollection<PointViewModel>();
            this.lines = new ObservableCollection<LineViewModel>();

            InitializeComponent();

            this.EllipseRadiuses = new Size(100, 50);
            this.SegmentStart = new Point(250, 250);
            //this.SegmentEnd = new Point(300, 300);
            this.SegmentEnd = new Point(320, 210);
            this.IsLargeArc = true;
            this.SweepDirection = System.Windows.Media.SweepDirection.Clockwise;
            this.XAxisRotation = 0;
        }

        public Geometry PathData
        {
            get
            {
                return this.pathData;
            }
            set
            {
                if (this.pathData != value)
                {
                    this.pathData = value;
                    this.OnPropertyChanged("PathData");
                }
            }
        }

        public double XAxisRotation
        {
            get
            {
                return this.xAxisRotation;
            }
            set
            {
                if (this.xAxisRotation != value)
                {
                    this.xAxisRotation = value;
                    this.OnPropertyChanged("XAxisRotation");
                    this.Recalculate();
                }
            }
        }

        public bool IsLargeArc
        {
            get
            {
                return this.isLargeArc;
            }
            set
            {
                if (this.isLargeArc != value)
                {
                    this.isLargeArc = value;
                    this.OnPropertyChanged("IsLargeArc");
                    this.Recalculate();
                }
            }
        }

        public SweepDirection SweepDirection
        {
            get
            {
                return this.sweepDirection;
            }
            set
            {
                if (this.sweepDirection != value)
                {
                    this.sweepDirection = value;
                    this.OnPropertyChanged("SweepDirection");
                    this.Recalculate();
                }
            }
        }

        public Size EllipseRadiuses
        {
            get
            {
                return this.ellipseRadiuses;
            }
            set
            {
                if (this.ellipseRadiuses != value)
                {
                    this.ellipseRadiuses = value;
                    this.OnPropertyChanged("EllipseRadiuses");
                    this.EllipseSize = new Size(value.Width * 2, value.Height * 2);
                    this.BoundingRadius = Math.Max(value.Width, value.Height);
                    this.Recalculate();
                }
            }
        }

        public double BoundingRadius
        {
            get
            {
                return this.boundingRadius;
            }
            set
            {
                if (this.boundingRadius != value)
                {
                    this.boundingRadius = value;
                    this.OnPropertyChanged("BoundingRadius");
                    this.BoundingDiameter = value * 2;
                    this.CanvasOffset = new Point(value + Padding, value + Padding);
                }
            }
        }

        public double BoundingDiameter
        {
            get
            {
                return this.boundingDiameter;
            }
            set
            {
                if (this.boundingDiameter != value)
                {
                    this.boundingDiameter = value;
                    this.OnPropertyChanged("BoundingDiameter");
                }
            }
        }

        public ObservableCollection<PointViewModel> Points
        {
            get
            {
                return this.points;
            }
        }

        public ObservableCollection<LineViewModel> Lines
        {
            get
            {
                return this.lines;
            }
        }

        public Point SegmentStart
        {
            get
            {
                return this.segmentStart;
            }
            set
            {
                if (this.segmentStart != value)
                {
                    this.segmentStart = value;
                    this.OnPropertyChanged("SegmentStart");
                    this.Recalculate();
                }
            }
        }

        public Point SegmentEnd
        {
            get
            {
                return this.segmentEnd;
            }
            set
            {
                if (this.segmentEnd != value)
                {
                    this.segmentEnd = value;
                    this.OnPropertyChanged("SegmentEnd");
                    this.Recalculate();
                }
            }
        }

        public Size EllipseSize
        {
            get
            {
                return this.ellipseSize;
            }
            set
            {
                if (this.ellipseSize != value)
                {
                    this.ellipseSize = value;
                    this.OnPropertyChanged("EllipseSize");
                }
            }
        }

        public Point CanvasOffset
        {
            get
            {
                return this.canvasOffset;
            }
            set
            {
                if (this.canvasOffset != value)
                {
                    this.canvasOffset = value;
                    this.OnPropertyChanged("CanvasOffset");
                }
            }
        }

        private void Recalculate()
        {
            this.Points.Clear();
            this.Lines.Clear();

            this.AddPoint(this.SegmentStart, 4);
            this.AddPoint(this.SegmentEnd, 10);

            Point segmentVector = this.SegmentEnd.Minus(this.SegmentStart);
            if (segmentVector == new Point())
            {
                return;
            }

            this.RecalculateHelperGeometry();

            this.RecalculatePathGeometry();
        }

        private void RecalculateHelperGeometry()
        {
            this.AddLine(new Point(-this.EllipseRadiuses.Width, 0), new Point(this.EllipseRadiuses.Width, 0));
            Point segmentVector = this.SegmentEnd.Minus(this.SegmentStart);

            Tuple<Point, Point> ellipseIntersections = ArcHelper.GetEllipseArcEndPoints(this.EllipseRadiuses, segmentVector, this.IsLargeArc, this.SweepDirection);
            if (ellipseIntersections != null)
            {
                this.AddLine(ellipseIntersections.Item1, ellipseIntersections.Item2, addEndPoints: true).Color = new SolidColorBrush(Colors.Green);
                Tuple<Point, Point> pointsOnCircle = ArcHelper.TranslatePointsToBoundingCircle(this.EllipseRadiuses, ellipseIntersections);
                this.AddLine(pointsOnCircle.Item1, pointsOnCircle.Item2, addEndPoints: true).Color = new SolidColorBrush(Colors.Transparent);

                foreach (Tuple<Point, Point> arcSegment in ArcHelper.DivideCircleArcToSmallerArcs(pointsOnCircle, this.SweepDirection, this.isLargeArc))
                {
                    this.AddLine(arcSegment.Item1, arcSegment.Item2, 1).Color = new SolidColorBrush(Colors.Red);
                    var bezierPoints = ArcHelper.GetArcCubicBezierApproximation(arcSegment, this.SweepDirection);
                    this.AddLine(bezierPoints.Item1, bezierPoints.Item2, 2).Color = new SolidColorBrush(Colors.Red);
                    this.AddLine(bezierPoints.Item3, bezierPoints.Item4, 2).Color = new SolidColorBrush(Colors.Red);
                }
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("NO CROSS POINTS!");
            }
        }

        private void RecalculatePathGeometry()
        {
            PathGeometry pathGeometry = new PathGeometry();
            PathFigure figure = new PathFigure();
            figure.IsClosed = false;
            figure.StartPoint = new Point();
            pathGeometry.Figures.Add(figure);
            figure.Segments.Add(new LineSegment(this.SegmentStart, false));

            foreach (var bezierArc in ArcHelper.GetEllipticArcCubicBezierApproximation(this.SegmentStart, this.SegmentEnd, this.EllipseRadiuses, this.IsLargeArc, this.SweepDirection, this.XAxisRotation))
            {
                figure.Segments.Add(new BezierSegment(bezierArc.Item2, bezierArc.Item3, bezierArc.Item4, true));
                this.AddLine(bezierArc.Item1, bezierArc.Item4, 1).Color = new SolidColorBrush(Colors.Red);
                this.AddLine(bezierArc.Item1, bezierArc.Item2, 2).Color = new SolidColorBrush(Colors.Purple);
                this.AddLine(bezierArc.Item3, bezierArc.Item4, 2).Color = new SolidColorBrush(Colors.Purple);
            }

            this.PathData = pathGeometry;
        }

        private PointViewModel AddPoint(Point point, double diameter = 8)
        {
            PointViewModel pointVM = new PointViewModel(point, diameter);
            this.Points.Add(pointVM);

            return pointVM;
        }

        private LineViewModel AddLine(Point start, Point end, double thickness = 2, bool addEndPoints = false)
        {
            LineViewModel lineVM = new LineViewModel(start, end, thickness);

            if (addEndPoints)
            {
                this.AddPoint(start);
                this.AddPoint(end);
            }

            this.Lines.Add(lineVM);

            return lineVM;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
